package com.maixy.rabbitmqdemo.peck;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * @ClassName DelayRabbitConfig
 * @Description 延迟队列配置类
 * @Date 2021/3/23 20:52
 * @Author maixy
 */
@Configuration
@Slf4j
public class PeckRabbitConfig {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 交换机名称
     */
    public static final String PECK_EXCHANGE_1 = "peck_exchange_1";

    /**
     * 死信交换机名称
     */
    public static final String PECK_DEAD_EXCHANGE_1 = "peck_dead_exchange_1";

    /**
     * 队列名称
     */
    public static final String PECK_QUEUE_1 = "peck_queue_1";
    public static final String PECK_QUEUE_2 = "peck_queue_2";

    /**
     * 死信队列名称
     */
    public static final String PECK_DEAD_QUEUE_1 = "peck_dead_queue_1";


    @Bean
    public Queue peckQueue1() {
        //return new Queue(PECK_QUEUE_1, true);
        return QueueBuilder
                .durable(PECK_QUEUE_1)
                //声明该队列的死信消息发送到的交换机（队列添加了这个参数之后会自动与该交换机绑定，并设置路由键，不需要开发者手动设置)
                .withArgument("x-dead-letter-exchange", PECK_DEAD_EXCHANGE_1)
                //声明该队列死信消息在交换机的路由键
                .withArgument("x-dead-letter-routing-key", "peck-dead-letter-routing-key")
                .build();
    }

    @Bean
    public Queue peckQueue2() {
        //return new Queue(PECK_QUEUE_1, true);
        return QueueBuilder
                .durable(PECK_QUEUE_2)
                //声明该队列的死信消息发送到的 交换机 （队列添加了这个参数之后会自动与该交换机绑定，并设置路由键，不需要开发者手动设置)
                .withArgument("x-dead-letter-exchange", PECK_DEAD_EXCHANGE_1)
                //声明该队列死信消息在交换机的 路由键
                .withArgument("x-dead-letter-routing-key", "peck-dead-letter-routing-key")
                .build();
    }


    /**
     * 正常交换机
     * @return org.springframework.amqp.core.TopicExchange
     */
    @Bean
    public TopicExchange peckExchange1() {
        return new TopicExchange(PECK_EXCHANGE_1, true, false);
    }

    /**
     * 正常交换机与正常队列绑定
     * @param peckQueue1 正常队列
     * @param peckExchange1 正常交换机
     * @return org.springframework.amqp.core.Binding
     */
    @Bean
    public Binding peckBinding1(Queue peckQueue1, TopicExchange peckExchange1) {
        return BindingBuilder.bind(peckQueue1).to(peckExchange1).with("peck.*");
    }

    @Bean
    public Binding peckBinding2(Queue peckQueue2, TopicExchange peckExchange1) {
        return BindingBuilder.bind(peckQueue2).to(peckExchange1).with("peck.*");
    }


    /**
     * 死信交换机
     * @return org.springframework.amqp.core.TopicExchange
     */
    @Bean
    public TopicExchange peckDeadExchange1() {
        return new TopicExchange(PECK_DEAD_EXCHANGE_1, true, false);
    }


    /**
     * 死信队列
     * 正常队列peck_queue_1的死信投递到死信交换机后再投递到该队列
     * @return org.springframework.amqp.core.Queue
     */
    @Bean
    public Queue peckDeadQueue1() {
        return new Queue(PECK_DEAD_QUEUE_1, true);
    }

    /**
     * 死信队列绑定死信交换机
     * @param peckDeadQueue1 死信队列
     * @param peckDeadExchange1 死信交换机
     * @return org.springframework.amqp.core.Binding
     */
    @Bean
    public Binding peckDeadBinding1(Queue peckDeadQueue1, TopicExchange peckDeadExchange1) {
        return BindingBuilder.bind(peckDeadQueue1).to(peckDeadExchange1).with("peck-dead-letter-routing-key");
    }

    /**
     * 初始化消息发送确认回调的方法
     * 说明：被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行，并且只会被服务器执行一次。
     *      如果想在生成对象时完成某些初始化操作，而偏偏这些初始化操作又依赖于依赖注入，那么久无法在构造函数中实现。
     *      为此，可以使用@PostConstruct注解一个方法来完成初始化，@PostConstruct注解的方法将会在依赖注入完成后被自动调用。
     * rabbitmq 整个消息投递的路径为：
     * producer—>rabbitmq broker—>exchange—>queue—>consumer
     */
    @PostConstruct
    private void init() {
        //当消息从producer到exchange会回调这个方法
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean b, String s) {
                if (b) {
                    log.info("producer发送消息到exchange成功，消息内容是[{}]",s);
                } else {
                    log.error("producer发送消息到exchange失败，消息内容是[{}]",s);
                }
            }
        });
        //当消息从exchange路由到queue失败后会回调这个方法
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int i, String s, String s1, String s2) {
                log.error("消息从exchange发送到queue失败，消息内容是[{}]", s);
            }
        });
    }
}
